﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using MathNet.Numerics.LinearAlgebra;

using cit.CopyDetection.Model;
using cit.CopyDetection.DataObject;

namespace cit.CopyDetection.DatabaseAccess
{
    /// <summary>
    /// Đọc và ghi dữ liệu ra tập tin XML
    /// </summary>
    public class ModelDataAccess
    {
        /// <summary>
        /// Ghi tập tự vựng ra file XML
        /// </summary>
        /// <param name="path">Địa chỉ ghi file</param>
        /// <param name="vocabulary">Tập từ vựng cần ghi file</param>
        public static void WriteVocabulary(string path, List<Term> vocabulary)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;

            using (XmlWriter writer = XmlWriter.Create(path, settings))
            {
                writer.WriteStartElement("Vocabulary");
                writer.WriteAttributeString("nterms", vocabulary.Count.ToString());
                foreach (Term term in vocabulary)
                {
                    writer.WriteStartElement("Term");
                    writer.WriteAttributeString("parafreq", term.ParagraphFrequency.ToString());
                    writer.WriteAttributeString("globalfreq", term.GlobalFrequency.ToString());
                    writer.WriteString(term.Value);
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();
            }
        }

        /// <summary>
        /// Đọc tập từ vựng từ file XML
        /// </summary>
        /// <param name="path">Địa chỉ file XML từ vựng</param>
        /// <returns>Tập từ vựng đọc được từ file</returns>
        public static List<Term> ReadVocabulary(string path)
        {
            List<Term> vocabulary = new List<Term>();
            using (XmlReader reader = XmlReader.Create(path))
            {
                reader.ReadToFollowing("Vocabulary");
                int nterms = int.Parse(reader.GetAttribute("nterms")); 
                for (int i = 0; i < nterms; i++)
                {                    
                    Term term = new Term();

                    reader.ReadToFollowing("Term");
                    term.ParagraphFrequency = int.Parse(reader.GetAttribute("parafreq"));
                    term.GlobalFrequency = int.Parse(reader.GetAttribute("globalfreq"));
                    term.Value = reader.ReadString();

                    vocabulary.Add(term);
                }
                reader.Read();
                reader.ReadEndElement();
            }

            return vocabulary;
        }

        /// <summary>
        /// Ghi mô hình vector ra file XML
        /// </summary>
        /// <param name="path">Đường dẫn file</param>
        /// <param name="localWtFunc">Hàm đánh trọng số từ cục bộ sử dụng</param>
        /// <param name="globalWtFunc">Hàm đánh trọng số từ toàn cục sử dụng</param>
        /// <param name="paraModels">Thông tin và các vector đoạn văn bản</param>
        /// <param name="positions">Vị trí các tài liệu trong ma trận</param>
        public static void WriteModel(
            string path, 
            string localWtFunc, string globalWtFunc, 
            List<ParagraphModel> paraModels, Dictionary<Document, Position> positions) 
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;            
            
            Matrix matrix = MatrixExtension.CreateMatrix(paraModels);
            SingularValueDecomposition SVD = matrix.SingularValueDecomposition;
            Matrix leftSingularVectors = SVD.LeftSingularVectors;
            Matrix rightSingularVectors = SVD.RightSingularVectors;
            Vector singularValues = SVD.SingularValues;

            using (XmlWriter writer = XmlWriter.Create(path, settings))
            {
                writer.WriteStartElement("Model");

                // Write weighting fuctions
                writer.WriteStartElement("WtScheme");
                writer.WriteElementString("LocalWtFunc", localWtFunc);
                writer.WriteElementString("GlobalWtFunc", globalWtFunc);
                writer.WriteEndElement();

                // Write documents
                int ndocuments = positions.Count;
                writer.WriteStartElement("Documents");
                writer.WriteAttributeString("ndocuments", ndocuments.ToString());
                foreach (KeyValuePair<Document, Position> position in positions)
                {
                    Document document = position.Key;
                    Position pos = position.Value;

                    writer.WriteStartElement("Document");
                    writer.WriteAttributeString("documentid", document.Name);
                    writer.WriteAttributeString("location", document.Location);
                    writer.WriteAttributeString("begin", pos.Begin.ToString());
                    writer.WriteAttributeString("end", pos.End.ToString());
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();

                // Write matrix and vector
                WriteMatrix("LeftSingularVectors", leftSingularVectors, writer);
                WriteMatrix("RightSingularVectors", rightSingularVectors, writer);
                WriteVector("SingularValues", singularValues, writer);

                writer.WriteEndElement();
            }
        }

        /// <summary>
        /// Đọc mô hình vector từ file XML
        /// </summary>
        /// <param name="path">Đường dẫn file</param>
        /// <param name="localWtFunc">Hàm đánh trọng số từ cục bộ sử dụng</param>
        /// <param name="globalWtFunc">Hàm đánh trọng số từ toàn cục sử dụng</param>
        /// <param name="leftSingularVectors">Ma trận vector đơn trái của SVD</param>
        /// <param name="rightSingularVectors">Ma trận vector đơn phải của SVD</param>
        /// <param name="singularValues">Vector các giá trị đơn của SVD</param>
        /// <returns>Thông tin và vị trí của các tài liệu trong mô hình</returns>
        public static Dictionary<Document, Position> ReadModel(
            string path, 
            out string localWtFunc, out string globalWtFunc,
            out Matrix leftSingularVectors, out Matrix rightSingularVectors, out Vector singularValues)
        {
            Dictionary<Document, Position> positions; 
            using (XmlReader reader = XmlReader.Create(path))
            {
                reader.ReadStartElement("Model");

                // Read weighting functions
                reader.ReadStartElement("WtScheme");

                reader.ReadStartElement("LocalWtFunc");
                localWtFunc = reader.ReadContentAsString();
                reader.ReadEndElement();

                reader.ReadStartElement("GlobalWtFunc");
                globalWtFunc = reader.ReadContentAsString();
                reader.ReadEndElement();

                reader.ReadEndElement();

                // Read documents
                positions = new Dictionary<Document, Position>();
                reader.ReadToFollowing("Documents");
                int ndocuments = int.Parse(reader.GetAttribute("ndocuments"));
                reader.Read();
                for (int i = 0; i < ndocuments; i++)
                {
                    Document document = new Document();
                    Position position = new Position();

                    reader.ReadToFollowing("Document");

                    document.Name = reader.GetAttribute("documentid");
                    document.Location = reader.GetAttribute("location");

                    position.Begin = int.Parse(reader.GetAttribute("begin"));
                    position.End = int.Parse(reader.GetAttribute("end"));

                    positions.Add(document, position);
                }
                reader.Read();
                reader.ReadEndElement();

                // Read matrix and vector
                leftSingularVectors = ReadMatrix("LeftSingularVectors", reader);
                rightSingularVectors = ReadMatrix("RightSingularVectors", reader);
                singularValues = ReadVector("SingularValues", reader);

                reader.ReadEndElement();
            }

            return positions;
        }

        /// <summary>
        /// Ghi ma trận ra file XML
        /// </summary>
        /// <param name="name">Tên nút bắt đầu</param>
        /// <param name="matrix">Ma trận cần ghi ra file</param>
        /// <param name="writer">Đối tượng XMLWriter liên kết với file</param>
        private static void WriteMatrix(string name, Matrix matrix, XmlWriter writer)
        {
            int columns = matrix.ColumnCount;
            int rows = matrix.RowCount;

            writer.WriteStartElement(name);

            writer.WriteStartElement("Columns");
            writer.WriteValue(columns);
            writer.WriteEndElement();

            writer.WriteStartElement("Rows");
            writer.WriteValue(rows);
            writer.WriteEndElement();

            writer.WriteStartElement("Elements");
            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < columns; j++)
                {
                    writer.WriteStartElement("Element");
                    writer.WriteValue(matrix[i, j]);
                    writer.WriteEndElement();
                }
            }
            writer.WriteEndElement();

            writer.WriteEndElement();
        }

        /// <summary>
        /// Đoc ma trận từ file XML
        /// </summary>
        /// <param name="name">Tên nút bắt đầu</param>
        /// <param name="reader">Đối tượng XMLReader liên kết với file</param>
        /// <returns>Ma trận đọc được từ file</returns>
        private static Matrix ReadMatrix(string name, XmlReader reader)
        {
            reader.ReadStartElement(name);

            reader.ReadStartElement("Columns");
            int columns = reader.ReadContentAsInt();
            reader.ReadEndElement();

            reader.ReadStartElement("Rows");
            int rows = reader.ReadContentAsInt();
            reader.ReadEndElement();

            Matrix m = new Matrix(rows, columns);

            reader.ReadStartElement("Elements");
            for (int i = 0; i < rows; i++)
                for (int j = 0; j < columns; j++)
                {
                    reader.ReadStartElement("Element");
                    m[i, j] = reader.ReadContentAsDouble();
                    reader.ReadEndElement();
                }
            reader.ReadEndElement();

            reader.ReadEndElement();

            return m;
        }

        /// <summary>
        /// Ghi vector ra file XML
        /// </summary>
        /// <param name="name">Tên nút bắt đầu</param>
        /// <param name="vector">Vector cần ghi ra file</param>
        /// <param name="writer">Đối tượng XMLWriter liên kết với file XML</param>
        private static void WriteVector(string name, Vector vector, XmlWriter writer)
        {
            int dim = vector.Length;

            writer.WriteStartElement(name);

            writer.WriteStartElement("Dimension");
            writer.WriteValue(dim);
            writer.WriteEndElement();

            writer.WriteStartElement("Elements");
            for (int i = 0; i < dim; i++)
            {
                writer.WriteStartElement("Element");
                writer.WriteValue(vector[i]);
                writer.WriteEndElement();
            }
            writer.WriteEndElement();

            writer.WriteEndElement();
        }

        /// <summary>
        /// Đọc vector từ file XML
        /// </summary>
        /// <param name="name">Tên nút bắt đầu</param>
        /// <param name="reader">Đối tượng XMLReader liên kết với file XML</param>
        /// <returns>Vector đọc được từ file</returns>
        private static Vector ReadVector(string name, XmlReader reader)
        {
            reader.ReadStartElement(name);

            reader.ReadStartElement("Dimension");
            int dim = reader.ReadContentAsInt();
            reader.ReadEndElement();

            Vector vector = new Vector(dim);

            reader.ReadStartElement("Elements");
            for (int i = 0; i < dim; i++)
            {
                reader.ReadStartElement("Element");
                vector[i] = reader.ReadContentAsDouble();
                reader.ReadEndElement();
            }
            reader.ReadEndElement();

            reader.ReadEndElement();

            return vector;
        }
    }
}
